/************************************************************
	File:		Ship.cpp
	Author:		Doug Monroe (dmonroe@fullsail.com)
	Course:		SGD 1304
	Purpose:	CShip class handles the user's ship entity
************************************************************/


#include "Ship.h"

#include "../../SGD Wrappers/CSGD_Direct3D.h"
#include "../../SGD Wrappers/CSGD_DirectInput.h"
#include "../../SGD Wrappers/CSGD_TextureManager.h"
#include "../../SGD Wrappers/CSGD_XAudio2.h"
#include "../../SGD Wrappers/CSGD_EventSystem.h"
#include "../../SGD Wrappers/CSGD_MessageSystem.h"
#include "../../SGD Wrappers/SGD_String.h"
#include "../../SGD Wrappers/SGD_Math.h"

#include "Game.h"
#include "CreateLaserMessage.h"

#include <cassert>


//***********************************************************
// CONSTRUCTOR
CShip::CShip( void )
{
	// Set the entity type!
	CEntity::m_eType	= ENT_SHIP;

	// Set the data members
	m_nHealth			= 100;
	m_nAmmo				= 100;
	m_nScore			= 0;
	m_fRespawnTimer		= 0.0f;
		
	m_nThrustState		= THRUST_NONE;
	
	// load sfx
	CSGD_XAudio2* pXA	= CSGD_XAudio2::GetInstance();
	m_nShipThrustSfxID	= pXA->SFXLoadSound( _T("../resource/sounds/SGD_ShipThrust.wav") );
	m_nShipTurnSfxID	= pXA->SFXLoadSound( _T("../resource/sounds/SGD_ShipTurn.wav") );


	// Register for the "INCREASE_SCORE" event
	CSGD_EventSystem::GetInstance()->RegisterClient( "INCREASE_SCORE", this );
}


//***********************************************************
// DESTRUCTOR
CShip::~CShip( void )
{
	// Unload sfx
	CSGD_XAudio2::GetInstance()->SFXStopSound( m_nShipThrustSfxID );
	CSGD_XAudio2::GetInstance()->SFXUnloadSound( m_nShipThrustSfxID );
	m_nShipThrustSfxID = -1;
	
	CSGD_XAudio2::GetInstance()->SFXStopSound( m_nShipTurnSfxID );
	CSGD_XAudio2::GetInstance()->SFXUnloadSound( m_nShipTurnSfxID );
	m_nShipTurnSfxID = -1;

	// Unregister from ALL events
	CSGD_EventSystem::GetInstance()->UnregisterClientAll( this );
}


//***********************************************************
// Update
//	- move the ship
/*virtual*/ void CShip::Update( float fElapsedTime )
{
	CSGD_DirectInput* pDI = CSGD_DirectInput::GetInstance();


	// Dead?
	if( m_nHealth <= 0 )
	{
		if( pDI->KeyPressed( DIK_RETURN ) == true )
		{
			m_nHealth		= 100;
			m_fRespawnTimer	= 3.0f;
			SetPosX( float((CGame::GetInstance()->GetScreenWidth() - this->GetWidth())/2) );
			SetPosY( float((CGame::GetInstance()->GetScreenHeight() - this->GetHeight())/2) );
			SetVelX( 0.0f );
			SetVelY( 0.0f );
		}

		return;
	}

	// Respawning?
	if( m_fRespawnTimer > 0.0f )
		m_fRespawnTimer -= fElapsedTime;


	// Fire laser?
	if( m_nAmmo > 0 && pDI->KeyPressed( DIK_SPACE ) == true )
	{
		--m_nAmmo;

		// Send a CCreateLaserMessage, with the ship's center point
		// to the Message System
		CCreateLaserMessage* pMsg = new CCreateLaserMessage(
			GetPosX() + GetWidth()/2,
			GetPosY() + GetHeight()/2,
			GetRotation() );

		CSGD_MessageSystem::GetInstance()->SendMsg( pMsg );
		pMsg = nullptr;


		//// TRIPLE-SHOT:
		//pMsg = new CCreateLaserMessage(
		//	GetPosX() + GetWidth()/2,
		//	GetPosY() + GetHeight()/2,
		//	GetRotation() + D3DX_PI/4 );

		//CSGD_MessageSystem::GetInstance()->SendMsg( pMsg );
		//pMsg = nullptr;
		//
		//pMsg = new CCreateLaserMessage(
		//	GetPosX() + GetWidth()/2,
		//	GetPosY() + GetHeight()/2,
		//	GetRotation() - D3DX_PI/4 );

		//CSGD_MessageSystem::GetInstance()->SendMsg( pMsg );
		//pMsg = nullptr;
	}

	// Reload?
	if( pDI->KeyPressed( DIK_R ) == true )
	{
		m_nAmmo = 100;
	}


	// Spaceship Movement
	//	- ship rotates around its center
	//	- rotating does not affect velocity
	//	- thrusting will add velocity in the direction
	//	  the ship is facing
	m_nThrustState		= THRUST_NONE;

	if( pDI->KeyDown( DIK_LEFT ) == true )
	{
		SetRotation( GetRotation() - D3DX_PI/2 * fElapsedTime );
		m_nThrustState |= THRUST_LEFT_TURN;
	}
	else if( pDI->KeyDown( DIK_RIGHT ) == true )
	{
		SetRotation( GetRotation() + D3DX_PI/2 * fElapsedTime );
		m_nThrustState |= THRUST_RIGHT_TURN;
	}



	
	if( pDI->KeyDown( DIK_UP ) == true )
	{
		// Create a unit vector in the direction the image is facing
		tVector2D vOrientation = { 0, -1 };

		// Rotate the vector
		vOrientation = Vector2DRotate( vOrientation, GetRotation() );

		// Scale the vector
		vOrientation = vOrientation * 100;


		// Increase the velocity to ADD the acceleration
		SetVelX( GetVelX() + vOrientation.fX * fElapsedTime );
		SetVelY( GetVelY() + vOrientation.fY * fElapsedTime );

		// Cap the speed
		tVector2D vVelocity = { GetVelX(), GetVelY() };
		float fMagnitude = Vector2DLength( vVelocity );
		if( fMagnitude > 300 )
			vVelocity = vVelocity * (300 / fMagnitude);
		SetVelX( vVelocity.fX );
		SetVelY( vVelocity.fY );

		
		m_nThrustState |= THRUST_FORWARD;
	}
	else if( pDI->KeyDown( DIK_DOWN ) == true )
	{
		// Create a unit vector in the direction the image is facing
		tVector2D vOrientation = { 0, -1 };

		// Rotate the vector
		vOrientation = Vector2DRotate( vOrientation, GetRotation() );

		// Scale the vector
		vOrientation = vOrientation * 100;


		// Decrease the velocity to ADD the decceleration
		SetVelX( GetVelX() - vOrientation.fX * fElapsedTime );
		SetVelY( GetVelY() - vOrientation.fY * fElapsedTime );
		
		// Cap the speed
		tVector2D vVelocity = { GetVelX(), GetVelY() };
		float fMagnitude = Vector2DLength( vVelocity );
		if( fMagnitude > 300 )
			vVelocity = vVelocity * (300 / fMagnitude);
		SetVelX( vVelocity.fX );
		SetVelY( vVelocity.fY );

		
		m_nThrustState |= THRUST_REVERSE;
	}


	// Play / stop sfx
	CSGD_XAudio2* pXA = CSGD_XAudio2::GetInstance();

	if( m_nShipTurnSfxID != -1 )
	{
		// Turning sfx should only play while turning
		if( (m_nThrustState & (THRUST_LEFT_TURN | THRUST_RIGHT_TURN)) != 0 )
		{
			if( pXA->SFXIsSoundPlaying( m_nShipTurnSfxID ) == false )
				pXA->SFXPlaySound( m_nShipTurnSfxID );
		}
		else if( pXA->SFXIsSoundPlaying( m_nShipTurnSfxID ) == true )
			pXA->SFXStopSound( m_nShipTurnSfxID );
	}
	
	if( m_nShipThrustSfxID != -1 )
	{
		// Thrusting sfx should only play while thrusting (forward or reverse)
		if( (m_nThrustState & (THRUST_FORWARD | THRUST_REVERSE)) != 0 )
		{
			if( pXA->SFXIsSoundPlaying( m_nShipThrustSfxID ) == false )
				pXA->SFXPlaySound( m_nShipThrustSfxID );
		}
		else if( pXA->SFXIsSoundPlaying( m_nShipThrustSfxID ) == true )
			pXA->SFXStopSound( m_nShipThrustSfxID );
	}



	// Call the CEntity's Update to actually move
	CEntity::Update( fElapsedTime );




	// Wrap-around the screen
	int nScreenWidth = CGame::GetInstance()->GetScreenWidth();
	int nScreenHeight = CGame::GetInstance()->GetScreenHeight();

	if( GetPosX() + GetWidth() < 0 )		// left
	{
		SetPosX( nScreenWidth );
	}
	else if( GetPosX() > nScreenWidth )		// right
	{
		SetPosX( -GetWidth() );
	}
	
	if( GetPosY() + GetHeight() < 0 )		// top
	{
		SetPosY( nScreenHeight );
	}
	else if( GetPosY() > nScreenHeight )	// bottom
	{
		SetPosY( -GetHeight() );
	}
}


//***********************************************************
// Render
//	- render just the ship region of the image
/*virtual*/ void CShip::Render( void )
{
	// Assert that the image is valid
	assert( GetImageID() != -1 && "CShip::Render - ship image was not set" );

	CSGD_TextureManager* pTM = CSGD_TextureManager::GetInstance();

	// Respawn transparency
	DWORD dwColor = D3DCOLOR_ARGB( 255, 255, 255, 255 );
	
	if( m_fRespawnTimer > 0.0f )
		if( int(m_fRespawnTimer*10) % 6 < 3 )		// *shrug*?
			dwColor = D3DCOLOR_ARGB( 127, 255, 255, 255 );

	// Draw the ship region
	RECT rShipRegion = { 0, 0, 64, 64 };
	POINT ptCenter = { (rShipRegion.right - rShipRegion.left) / 2, (rShipRegion.bottom - rShipRegion.top) / 2 };

	pTM->Draw(	GetImageID(), (int)GetPosX(), (int)GetPosY(),
				1.0f, 1.0f, &rShipRegion,
				(float)ptCenter.x, (float)ptCenter.y, GetRotation(), dwColor );
	

	// Draw the engine exhaust
	if( m_nThrustState != THRUST_NONE )
	{
		// Draw engine exhausts
		RECT rSourceExhaust		= { 64,  0, 128, 64 };
		RECT rSourceExhaust_FL	= { 64,  0,  96, 32 };
		RECT rSourceExhaust_FR	= { 96,  0, 128, 32 };
		RECT rSourceExhaust_BL	= { 64, 32,  96, 64 };
		RECT rSourceExhaust_BR	= { 96, 32, 128, 64 };

		POINT ptOffset_FL	= { rSourceExhaust_FL.left - rSourceExhaust.left,	rSourceExhaust_FL.top - rSourceExhaust.top };
		POINT ptOffset_FR	= { rSourceExhaust_FR.left - rSourceExhaust.left,	rSourceExhaust_FR.top - rSourceExhaust.top };
		POINT ptOffset_BL	= { rSourceExhaust_BL.left - rSourceExhaust.left,	rSourceExhaust_BL.top - rSourceExhaust.top };
		POINT ptOffset_BR	= { rSourceExhaust_BR.left - rSourceExhaust.left,	rSourceExhaust_BR.top - rSourceExhaust.top };

		// Front left when reversing or turning left
		if( (m_nThrustState & (THRUST_REVERSE | THRUST_LEFT_TURN)) != 0 )
			pTM->Draw(	GetImageID(), (int)GetPosX()+ptOffset_FL.x, (int)GetPosY()+ptOffset_FL.y, 1.0f, 1.0f, &rSourceExhaust_FL, 
						(float)(ptCenter.x - ptOffset_FL.x), (float)(ptCenter.x - ptOffset_FL.y), GetRotation() );

		// Front right when reversing or turning right
		if( (m_nThrustState & (THRUST_REVERSE | THRUST_RIGHT_TURN)) != 0 )
			pTM->Draw(	GetImageID(), (int)GetPosX()+ptOffset_FR.x, (int)GetPosY()+ptOffset_FR.y, 1.0f, 1.0f, &rSourceExhaust_FR, 
						(float)(ptCenter.x - ptOffset_FR.x), (float)(ptCenter.x - ptOffset_FR.y), GetRotation() );
		
		// Back left when thrusting or turning right
		if( (m_nThrustState & (THRUST_FORWARD | THRUST_RIGHT_TURN)) != 0 )
			pTM->Draw(	GetImageID(), (int)GetPosX()+ptOffset_BL.x, (int)GetPosY()+ptOffset_BL.y, 1.0f, 1.0f, &rSourceExhaust_BL, 
						(float)(ptCenter.x - ptOffset_BL.x), (float)(ptCenter.x - ptOffset_BL.y), GetRotation() );

		// Back right when thrusting or turning left
		if( (m_nThrustState & (THRUST_FORWARD | THRUST_LEFT_TURN)) != 0 )
			pTM->Draw(	GetImageID(), (int)GetPosX()+ptOffset_BR.x, (int)GetPosY()+ptOffset_BR.y, 1.0f, 1.0f, &rSourceExhaust_BR, 
						(float)(ptCenter.x - ptOffset_BR.x), (float)(ptCenter.x - ptOffset_BR.y), GetRotation() );
	}


	// Draw stats:
	TOSTRINGSTREAM output;
	output << _T("Health: ") << m_nHealth
		   << _T("\nAmmo: ") << m_nAmmo
		   << _T("\nScore: ") << m_nScore;

	CSGD_Direct3D::GetInstance()->DrawText(
		output.str().c_str(), CGame::GetInstance()->GetScreenWidth() - 150, 5 );

	if( m_nAmmo == 0 )
		CSGD_Direct3D::GetInstance()->DrawText(
			_T("RELOAD"), 
			(CGame::GetInstance()->GetScreenWidth() - 100)/2, 
			(CGame::GetInstance()->GetScreenHeight() - 20)/2,
			D3DCOLOR_ARGB( 255, 255, 255, 0 ) );


	// Dead?
	if( m_nHealth <= 0 )
	{
		CSGD_Direct3D::GetInstance()->DrawText(
			_T("GAME OVER"),
			(CGame::GetInstance()->GetScreenWidth() - 100)/2, 
			(CGame::GetInstance()->GetScreenHeight() - 20)/2,
			D3DCOLOR_ARGB( 255, 255, 0, 0 ) );
	}
}


//***********************************************************
// CheckCollision
//	- CTarget - lose health
//	- CTurret - DEATH
//	- CLaser  - lose health
/*virtual*/ bool CShip::CheckCollision( IEntity* pOther )
{
	// Are we dead already?
	if( m_nHealth <= 0 )
		return false;

	// Are we invulnerable?
	if( m_fRespawnTimer > 0.0f )
		return false;


	// Did we collide?
	if( CEntity::CheckCollision( pOther ) )
	{
		// What did we hit?
		switch( pOther->GetType() )
		{
		case ENT_TARGET:
			{
				// Lose 5 hp
				m_nHealth -= 5;
			}
			break;
			
		case ENT_TURRET:
			{
				// DEATH
				m_nHealth = 0;
			}
			break;

		case ENT_LASER:
			{
				// Lose 1 hp
				m_nHealth -= 1;
			}
			break;

		case ENT_BASE:	// Generic entity?
		case ENT_SHIP:	// Other ships should not be possible
		default:		// wtf?
			return false;
		}

		return true;
	}

	return false;
}


//***********************************************************
// HandleEvent
//	- "INCREASE_SCORE"	- param is an int*
/*virtual*/ void CShip::HandleEvent( CEvent* pEvent )
{
	// What type of event?
	if( pEvent->GetEventID() == "INCREASE_SCORE" )
	{
		// Decode the event parameter
		int* pPoints = reinterpret_cast< int* >( pEvent->GetParam() );

		// Increase score!
		if( pPoints != nullptr )
			if( *pPoints > 0 )
				m_nScore += *pPoints;
	}
}
